package com.zp.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zp.api.aliyun.AliYunOutPaintingTask;
import com.zp.api.aliyun.domain.CreateOutPaintingTaskRequest;
import com.zp.api.aliyun.domain.CreateOutPaintingTaskResponse;
import com.zp.domain.entity.Picture;
import com.zp.domain.entity.Space;
import com.zp.domain.entity.User;
import com.zp.domain.file.UploadPictureResult;
import com.zp.domain.request.picture.*;
import com.zp.domain.vo.PictureVO;
import com.zp.domain.vo.UserVO;
import com.zp.enums.ErrorCode;
import com.zp.enums.PictureReviewStatusEnum;
import com.zp.exception.BusinessException;
import com.zp.manager.OssManager;
import com.zp.manager.uploadtemplate.FilePictureUpload;
import com.zp.manager.uploadtemplate.PictureUploadTemplate;
import com.zp.manager.uploadtemplate.URLPictureUpload;
import com.zp.mapper.PictureMapper;
import com.zp.service.PictureService;
import com.zp.service.SpaceService;
import com.zp.service.UserService;
import com.zp.utils.ColorSimilarUtils;
import com.zp.utils.ColorTransformUtils;
import com.zp.utils.ThrowUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;

import java.awt.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.List;
import java.util.stream.Collectors;


@Service
@RequiredArgsConstructor
@Slf4j
public class PictureServiceImpl extends ServiceImpl<PictureMapper, Picture> implements PictureService {
    private final UserService userService;
    private final FilePictureUpload filePictureUpload;
    private final URLPictureUpload urlPictureUpload;
    private final OssManager ossManager;
    private final SpaceService spaceService;
    private final TransactionTemplate transactionTemplate;
    private final AliYunOutPaintingTask aliYunOutPaintingTask;

    @Value("${aliYun.apiKey}")
    private String apiKey;

    /**
     * PictureVO 转 Picture
     *
     * @param pictureVO pictureVO
     * @return Picture
     */
    @Override
    public Picture getPicture(PictureVO pictureVO) {
        if (pictureVO == null) {
            return null;
        }
        Picture picture = new Picture();
        BeanUtil.copyProperties(pictureVO, picture);
        List<String> tagsList = pictureVO.getTags();
        String tags = JSONUtil.toJsonStr(tagsList);
        picture.setTags(tags);
        return picture;
    }

    /**
     * Picture 转 PictureVO
     *
     * @param picture picture
     * @return PictureVO
     */
    @Override
    public PictureVO getPictureVO(Picture picture) {
        if (picture == null) {
            return null;
        }
        PictureVO pictureVO = new PictureVO();
        BeanUtil.copyProperties(picture, pictureVO);
        String jsonTags = picture.getTags();
        List<String> tagsList = JSONUtil.toList(jsonTags, String.class);
        pictureVO.setTags(tagsList);
        return pictureVO;
    }

    /**
     * 上传图片
     *
     * @param t             本地文件 / URL
     * @param uploadRequest 上传参数
     * @param loginUser     当前登录用户
     * @return 图片信息(脱敏)
     */
    @Override
    public <T> PictureVO uploadPicture(T t, PictureUploadRequest uploadRequest, User loginUser) {
        ThrowUtils.throwIf(ObjUtil.isEmpty(loginUser), ErrorCode.NO_AUTH_ERROR);
        // ADD: 校验空间是否存在
        Long spaceId = uploadRequest.getSpaceId();
        if (spaceId != null) {
            Space space = spaceService.getById(spaceId);
            ThrowUtils.throwIf(ObjUtil.isNull(space), ErrorCode.NOT_FOUND_ERROR, "空间不存在");
            // 空间存在, 仅空间创建人(空间管理员)可上传
//            if (!loginUser.getId().equals(space.getUserId())) {
//                throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "无权限操作该空间");
//            }
            // 校验额度
            if (space.getTotalCount() >= space.getMaxCount()) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "空间图片数量已满");
            }
            if (space.getTotalSize() >= space.getMaxSize()) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "空间大小不足");
            }
        }
        Long pictureId;
        pictureId = uploadRequest.getId();
        // 1. pictureId 存在, 更新图片(进行校验)
        if (pictureId != null) {
            // 1. 判断图片是否存在
            Picture OldPicture = this.getById(pictureId);
            ThrowUtils.throwIf(OldPicture == null, ErrorCode.NOT_FOUND_ERROR, "图片不存在");
            // 2. 仅本人或管理员可修改
//            Long userId = loginUser.getId();
//            if (!userId.equals(OldPicture.getUserId()) && !userService.isAdmin(loginUser)) {
//                throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
//            }
            // ADD: 校验空间是否一致
            if (spaceId == null) {
                // 没传spaceId, 使用原来图片的 spaceId
                spaceId = OldPicture.getSpaceId();
            } else {
                // 传入spaceId, 判断空间是否一致
                if (ObjUtil.notEqual(spaceId, OldPicture.getSpaceId())) {
                    throw new BusinessException(ErrorCode.PARAMS_ERROR, "空间不一致");
                }
            }
        }
        // 2. pictureId 不存在, 上传图片
        // 划分目录
        String uploadPathPrefix;
        if (spaceId == null) {
            // 公共图库, 以上传用户的 id 划分目录
            uploadPathPrefix = String.format("public/%s", loginUser.getId());
        } else {
            // 私有图库, 使用空间 id 划分目录
            uploadPathPrefix = String.format("space/%s", spaceId);
        }
        // 根据类型区分上传方式
        PictureUploadTemplate template = filePictureUpload;
        if (t instanceof String) {
            template = urlPictureUpload;
        }
        UploadPictureResult uploadPictureResult = template.uploadPicture(t, uploadPathPrefix);
        Picture picture = new Picture();
        // 批量抓取图片
        String picName = uploadPictureResult.getPicName();
        // 从外部传入图片名称
        if (StrUtil.isNotBlank(uploadRequest.getPicName())) {
            picName = uploadRequest.getPicName();
        }
        // 构造入库的图片信息
        picture.setUrl(uploadPictureResult.getUrl());
        picture.setName(picName);
        picture.setPicSize(uploadPictureResult.getPicSize());
        picture.setPicWidth(uploadPictureResult.getPicWidth());
        picture.setPicHeight(uploadPictureResult.getPicHeight());
        picture.setPicScale(uploadPictureResult.getPicScale());
        picture.setPicFormat(uploadPictureResult.getPicFormat());
        picture.setUserId(loginUser.getId());
        picture.setSpaceId(spaceId);
        // 保存图片主色调到数据库
        picture.setPicColor(ColorTransformUtils.getStandardColor(uploadPictureResult.getPicColor()));
        // 填充审核参数
        this.fillReviewParams(picture, loginUser);
        if (pictureId != null) {
            // 更新图片, 补充 id 和编辑时间
            picture.setEditTime(new Date());
            picture.setId(pictureId);
        }
        Long finalSpaceId = spaceId;
        transactionTemplate.execute(status -> {
            // 插入数据库
            boolean saved = this.saveOrUpdate(picture);
            ThrowUtils.throwIf(!saved, ErrorCode.SYSTEM_ERROR, "保存图片失败");
            if (finalSpaceId != null) {
                // 更新空间额度
                boolean update = spaceService.lambdaUpdate()
                        .eq(Space::getId, finalSpaceId)
                        .setSql("total_size = total_size + " + picture.getPicSize())
                        .setSql("total_count = total_count + 1")
                        .update();
                ThrowUtils.throwIf(!update, ErrorCode.SYSTEM_ERROR, "更新空间额度失败");
            }
            return picture;
        });
        return this.getPictureVO(picture);
    }

    /**
     * 删除图片
     *
     * @param pictureId 图片 id
     * @param loginUser 当前登录用户
     */
    @Override
    public void deletePicture(long pictureId, User loginUser) {
        ThrowUtils.throwIf(loginUser == null, ErrorCode.NOT_LOGIN_ERROR);
        ThrowUtils.throwIf(pictureId <= 0, ErrorCode.PARAMS_ERROR);
        // 判断图片是否存在
        Picture oldPicture = this.getById(pictureId);
        ThrowUtils.throwIf(ObjUtil.isNull(oldPicture), ErrorCode.NOT_FOUND_ERROR, "图片不存在");
        // 校验权限
        // this.checkPictureAuth(loginUser, oldPicture);
        // 删除图片
        transactionTemplate.execute(status -> {
            boolean removed = this.removeById(pictureId);
            ThrowUtils.throwIf(!removed, ErrorCode.OPERATION_ERROR, "删除图片失败");
            if (oldPicture.getSpaceId() != null) {
                // 更新额度
                boolean update = spaceService.lambdaUpdate()
                        .eq(Space::getId, oldPicture.getSpaceId())
                        .setSql("total_size = total_size - " + oldPicture.getPicSize())
                        .setSql("total_count = total_count - 1")
                        .update();
                ThrowUtils.throwIf(!update, ErrorCode.SYSTEM_ERROR, "更新空间额度失败");
            }
            return true;
        });
        // 异步清理文件
        this.cleanPictureFile(oldPicture);
    }

    /**
     * 图片校验规则
     *
     * @param picture 图片
     */
    @Override
    public void validPicture(Picture picture) {
        if (picture == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Long pictureId = picture.getId();
        String pictureUrl = picture.getUrl();
        String introduction = picture.getIntroduction();
        ThrowUtils.throwIf(pictureId < 0, ErrorCode.PARAMS_ERROR);
        if (StrUtil.isNotBlank(pictureUrl)) {
            ThrowUtils.throwIf(pictureUrl.length() > 1024, ErrorCode.PARAMS_ERROR, "图片链接过长");
        }
        if (StrUtil.isNotBlank(introduction)) {
            ThrowUtils.throwIf(introduction.length() > 1024, ErrorCode.PARAMS_ERROR, "图片简介过长");
        }
    }

    /**
     * 补充审核参数
     *
     * @param picture   图片
     * @param loginUser 当前登录用户
     */
    @Override
    public void fillReviewParams(Picture picture, User loginUser) {
        ThrowUtils.throwIf(picture == null, ErrorCode.PARAMS_ERROR);
        ThrowUtils.throwIf(loginUser == null, ErrorCode.NOT_LOGIN_ERROR);
        if (userService.isAdmin(loginUser)) {
            // 当前登录用户为管理员, 自动过审
            picture.setReviewStatus(PictureReviewStatusEnum.PASS.getValue());
            picture.setReviewMessage("管理员自动过审");
            picture.setReviewerId(loginUser.getId());
            picture.setReviewTime(new Date());
        } else {
            // 普通用户, 需要进行审核
            picture.setReviewStatus(PictureReviewStatusEnum.REVIEWING.getValue());
        }
    }

    /**
     * 封装图片的分页查询条件
     *
     * @param request 查询参数
     * @return 查询条件
     */
    @Override
    public QueryWrapper<Picture> getQueryWrapper(PictureQueryRequest request) {
        QueryWrapper<Picture> wrapper = new QueryWrapper<>();
        if (request == null) {
            return wrapper;
        }
        // 1. 获取查询参数
        Long id = request.getId();
        String name = request.getName();
        String introduction = request.getIntroduction();
        String category = request.getCategory();
        List<String> tags = request.getTags(); // 单独处理
        Long picSize = request.getPicSize();
        Integer picWidth = request.getPicWidth();
        Integer picHeight = request.getPicHeight();
        Double picScale = request.getPicScale();
        String picFormat = request.getPicFormat();
        String searchText = request.getSearchText(); // 单独处理
        Long userId = request.getUserId();
        Integer reviewStatus = request.getReviewStatus();
        String reviewMessage = request.getReviewMessage();
        Long reviewerId = request.getReviewerId();
        String sortField = request.getSortField();
        String sortOrder = request.getSortOrder();
        Long spaceId = request.getSpaceId();
        boolean nullSpaceId = request.isNullSpaceId();
        Date startEditTime = request.getStartEditTime();
        Date endEditTime = request.getEndEditTime();
        // 2. 封装查询条件
        wrapper.eq(ObjUtil.isNotEmpty(id), "id", id);
        wrapper.like(StrUtil.isNotBlank(name), "name", name);
        wrapper.like(StrUtil.isNotBlank(introduction), "introduction", introduction);
        wrapper.eq(StrUtil.isNotBlank(category), "category", category);
        wrapper.eq(ObjUtil.isNotEmpty(picSize), "pic_size", picSize);
        wrapper.eq(ObjUtil.isNotEmpty(picWidth), "pic_width", picWidth);
        wrapper.eq(ObjUtil.isNotEmpty(picHeight), "pic_height", picHeight);
        wrapper.eq(ObjUtil.isNotEmpty(picScale), "pic_scale", picScale);
        wrapper.like(StrUtil.isNotBlank(picFormat), "pic_format", picFormat);
        wrapper.eq(userId != null && userId > 0, "user_id", userId);
        wrapper.eq(ObjUtil.isNotEmpty(reviewStatus), "review_status", reviewStatus);
        wrapper.like(StrUtil.isNotBlank(reviewMessage), "review_message", reviewMessage);
        wrapper.eq(reviewerId != null && reviewerId > 0, "reviewer_id", reviewerId);
        wrapper.eq(ObjUtil.isNotEmpty(spaceId), "space_id", spaceId);
        wrapper.isNull(nullSpaceId, "space_id");
        wrapper.ge(ObjUtil.isNotEmpty(startEditTime), "edit_time", startEditTime);
        wrapper.lt(ObjUtil.isNotEmpty(endEditTime), "edit_time", endEditTime);
        // tags 单独处理, 按 "标签1" 搜索
        if (CollUtil.isNotEmpty(tags)) {
            for (String tag : tags) {
                wrapper.like("tags", "\"" + tag + "\"");
            }
        }
        // 名称和简介可以同时搜
        if (StrUtil.isNotBlank(searchText)) {
            // where (name like '%searchText%' or introduction like '%searchText%') and isDelete = 0;
            wrapper.and(i -> i.like("name", searchText))
                    .or().like("introduction", searchText);
        }
        // 排序
        wrapper.orderBy(StrUtil.isNotBlank(sortField), sortOrder.equals("ascend"), sortField);
        return wrapper;
    }

    /**
     * 获取图片的 VO(处理 UserVO)
     *
     * @param picture 图片
     * @return 图片的 VO
     */
    @Override
    public PictureVO getPictureVOWithUserVO(Picture picture) {
        if (picture == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        PictureVO pictureVO = this.getPictureVO(picture);
        Long userId = picture.getUserId();
        if (userId != null && userId > 0) {
            User user = userService.getById(userId);
            UserVO userVO = userService.getUserVO(user);
            pictureVO.setUserVO(userVO);
        }
        return pictureVO;
    }

    /**
     * 分页获取图片的 VO (处理 UserVO)
     *
     * @param picturePage 图片的分页数据
     * @return 图片的 VO
     */
    @Override
    public Page<PictureVO> getPictureVOPage(Page<Picture> picturePage) {
        // 1. 获取图片列表
        List<Picture> pictureList = picturePage.getRecords();
        // 2. 封装为 VO
        Page<PictureVO> pictureVOPage = new Page<>(picturePage.getCurrent(), picturePage.getSize(), picturePage.getTotal());
        if (CollUtil.isEmpty(pictureList)) {
            return pictureVOPage;
        }
        // 3. 获取 VO 列表
        List<PictureVO> pictureVOList = pictureList.stream().map(this::getPictureVO).collect(Collectors.toList());
        // 4. 获取用户 id 集合
        Set<Long> userIdSet = pictureList.stream().map(Picture::getUserId).collect(Collectors.toSet());
        // 5. 获取用户集合
        List<User> userList = userService.listByIds(userIdSet);
        // 6. (userId, User) 进行分组
        Map<Long, List<User>> userMap = userList.stream().collect(Collectors.groupingBy(User::getId));
        // 7. 遍历填充数据
        for (PictureVO pictureVO : pictureVOList) {
            Long userId = pictureVO.getUserId();
            User user = null;
            if (userMap.containsKey(userId)) {
                user = userMap.get(userId).get(0);
            }
            UserVO userVO = userService.getUserVO(user);
            pictureVO.setUserVO(userVO);
        }
        pictureVOPage.setRecords(pictureVOList);
        return pictureVOPage;
    }

    /**
     * 用户图片编辑
     *
     * @param request   图片编辑请求
     * @param loginUser 登录用户
     */
    @Override
    public void editPicture(PictureEditRequest request, User loginUser) {
        ThrowUtils.throwIf(loginUser == null, ErrorCode.NOT_LOGIN_ERROR);
        // 1. 属性拷贝
        Picture picture = new Picture();
        BeanUtils.copyProperties(request, picture);
        // 2. 注意tags字段
        List<String> tagsList = request.getTags();
        String tags = JSONUtil.toJsonStr(tagsList);
        picture.setTags(tags);
        // 3. 设置编辑时间
        picture.setEditTime(new Date());
        // 4. 数据检验(合法性: url、简介等)
        this.validPicture(picture);
        // 5. 判断图片是否存在
        Picture oldPicture = this.getById(request.getId());
        if (oldPicture == null) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "图片不存在");
        }
        // 校验权限
        // this.checkPictureAuth(loginUser, oldPicture);
        // 6. 补充审核参数
        this.fillReviewParams(picture, loginUser);
        // 7. 修改图片
        boolean updated = this.updateById(picture);
        if (!updated) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "修改图片失败");
        }
    }

    /**
     * 管理员审核功能
     *
     * @param request   审核请求
     * @param loginUser 登录用户
     */
    @Override
    public void doPictureReview(PictureReviewRequest request, User loginUser) {
        // 1. 参数校验
        Long id = request.getId();
        Integer reviewStatus = request.getReviewStatus();
        PictureReviewStatusEnum reviewStatusEnum = PictureReviewStatusEnum.getEnumByValue(reviewStatus);
        if (id == null || reviewStatusEnum == null || PictureReviewStatusEnum.REVIEWING.equals(reviewStatusEnum)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        // 2. 判断图片是否存在
        Picture oldPicture = this.getById(id);
        if (oldPicture == null) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "图片不存在");
        }
        // 3. 不能重复审核
        if (oldPicture.getReviewStatus().equals(reviewStatus)) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "不能重复审核");
        }
        // 4. 执行审核状态
        Picture updatePicture = new Picture();
        BeanUtil.copyProperties(request, updatePicture);
        // 设置审核状态
        updatePicture.setReviewerId(loginUser.getId());
        updatePicture.setReviewTime(new Date());
        boolean updated = this.updateById(updatePicture);
        if (!updated) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "更新失败");
        }
    }

    /**
     * 批量抓取图片
     *
     * @param batchRequest 抓取请求参数
     * @param loginUser    登录用户
     * @return 抓取数量
     */
    @Override
    public Integer uploadPictureByBatch(PictureUploadByBatchRequest batchRequest, User loginUser) {
        ThrowUtils.throwIf(batchRequest.getCount() > 30, ErrorCode.PARAMS_ERROR, "最多抓取 30 张图片");
        ThrowUtils.throwIf(loginUser == null, ErrorCode.NOT_LOGIN_ERROR);
        // 1. 拼接抓取路径
        String searchText = batchRequest.getSearchText();
        String fetchUrl = String.format("https://cn.bing.com/images/async?q=%s&mmasync=1", searchText);
        // 2. 使用 Jsoup 抓取图片
        Document document;
        // 1. 建立连接
        try {
            document = Jsoup.connect(fetchUrl).get();
        } catch (Exception e) {
            log.error("连接失败", e);
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "连接失败");
        }
        // 2. 连接成功, 解析结果
        Element div = document.getElementsByClass("dgControl").first();
        if (ObjUtil.isNull(div)) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "获取图片失败");
        }
        // List
        Elements imgList = div.select(".iusc");
        // 遍历结果,依次处理
        int count = 0;
        for (Element ele : imgList) {
            String fileUrl = ele.attr("m");
            JSONObject obj = JSONUtil.parseObj(fileUrl);
            fileUrl = obj.getStr("murl");
            if (StrUtil.isBlank(fileUrl)) {
                log.error("图片地址为空, 跳过: {}", fileUrl);
                continue;
            }
            // 处理图片地址, 防止转义或 COS 冲突
            // https://www.baidu.com?a=1&b=2(去掉问号后的内容,包括问号)
            int index = fileUrl.indexOf("?");
            if (index > -1) {
                fileUrl = fileUrl.substring(0, index);
            }
            String namePrefix = batchRequest.getNamePrefix();
            if (StrUtil.isBlank(namePrefix)) {
                // 默认使用搜索词
                namePrefix = searchText;
            }
            PictureUploadRequest request = new PictureUploadRequest();
            request.setFileUrl(fileUrl);
            if (StrUtil.isNotBlank(namePrefix)) {
                request.setPicName(namePrefix + (count + 1));
            }
            // 上传图片
            try {
                PictureVO pictureVO = this.uploadPicture(fileUrl, request, loginUser);
                log.info("上传图片成功: {}", pictureVO);
                count++;
            } catch (Exception e) {
                log.error("上传图片失败: {}", e.getMessage());
                continue;
            }
            if (count >= batchRequest.getCount()) {
                break;
            }
        }
        return count;
    }

    /**
     * Redis + Caffeine 多级缓存
     *
     * @param queryRequest 查询参数
     * @return 分页结果
     */
    @Override
    public Page<PictureVO> listPictureVOByPageWithCache(PictureQueryRequest queryRequest) {
        // 4. 都不存在, 查询数据库
        int current = queryRequest.getCurrent();
        int pageSize = queryRequest.getPageSize();
        Page<Picture> picturePage = this.page(new Page<>(current, pageSize), this.getQueryWrapper(queryRequest));
        return this.getPictureVOPage(picturePage);
    }

    /**
     * 删除图片文件
     *
     * @param oldPicture 旧图片
     */
    @Async
    @Override
    public void cleanPictureFile(Picture oldPicture) {
        long count = this.lambdaQuery().eq(Picture::getUrl, oldPicture.getUrl()).count();
        // 有不止 1 条记录用到了该图片, 不清理
        if (count > 1) {
            return;
        }
        try {
            // 提取路径
            String path = new URL(oldPicture.getUrl()).getPath();
            ossManager.deleteObject(path);
        } catch (MalformedURLException e) {
            log.error("picture delete fail", e);

        }
    }

    /**
     * 检查图片权限
     *
     * @param loginUser 登录用户
     * @param picture   图片
     */
    @Override
    public void checkPictureAuth(User loginUser, Picture picture) {
        ThrowUtils.throwIf(loginUser == null, ErrorCode.NOT_LOGIN_ERROR);
        Long spaceId = picture.getSpaceId();
        if (spaceId == null) {
            // 公共图片, 仅本人或管理员可操作
            if (!picture.getUserId().equals(loginUser.getId()) && !userService.isAdmin(loginUser)) {
                throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
            }
        } else {
            // 私有图片, 仅空间管理员可操作
            if (!picture.getUserId().equals(loginUser.getId())) {
                throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
            }
        }
    }

    /**
     * 颜色搜索图片
     *
     * @param spaceId   空间 id
     * @param PicColor  图片主色调
     * @param loginUser 登录用户
     * @return 搜索到的图片列表
     */
    @Override
    public List<PictureVO> searchPictureByColor(Long spaceId, String PicColor, User loginUser) {
        // 1. 校验参数
        ThrowUtils.throwIf(spaceId == null, ErrorCode.PARAMS_ERROR);
        ThrowUtils.throwIf(loginUser == null, ErrorCode.NO_AUTH_ERROR);
        // 2. 校验空间权限
        Space space = spaceService.getById(spaceId);
        ThrowUtils.throwIf(space == null, ErrorCode.NOT_FOUND_ERROR, "空间不存在");
        if (!space.getUserId().equals(loginUser.getId()) && !userService.isAdmin(loginUser)) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
        }
        // 3. 查询包含主色调的图片
        List<Picture> pictureList = this.lambdaQuery().eq(Picture::getSpaceId, spaceId).isNotNull(Picture::getPicColor).list();
        if (CollUtil.isEmpty(pictureList)) {
            return Collections.emptyList();
        }
        // 4. 将十六进制颜色值转换为 Color 对象
        Color targetColor = Color.decode(PicColor);
        // 5. 计算相似度并排序, 返回结果
        return pictureList.stream()
                .sorted(Comparator.comparingDouble(picture -> {
                    // 提取图片主色调
                    String hexColor = picture.getPicColor();
                    // 没有主色调, 展示在最后
                    if (StrUtil.isBlank(hexColor)) {
                        return Double.MAX_VALUE;
                    }
                    // 转换为Color对象
                    Color pictureColor = Color.decode(hexColor);
                    // 默认是从小到大排序, 但是越大的越相似, 应该排在前面, 所以取负数
                    return -ColorSimilarUtils.calculateSimilarity(targetColor, pictureColor);
                }))
                .limit(12) // 限制数量
                .collect(Collectors.toList())
                .stream().map(this::getPictureVO)
                .collect(Collectors.toList());
    }

    /**
     * 批量操作图片
     *
     * @param request   图片批量操作请求
     * @param loginUser 登录用户
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void editPictureByBatch(PictureEditByBatchRequest request, User loginUser) {
        // 1. 参数校验
        ThrowUtils.throwIf(loginUser == null, ErrorCode.NOT_LOGIN_ERROR);
        List<Long> pictureIdList = request.getPictureIdList();
        Long spaceId = request.getSpaceId();
        String category = request.getCategory();
        List<String> tags = request.getTags();
        String nameRule = request.getNameRule();
        ThrowUtils.throwIf(CollUtil.isEmpty(pictureIdList), ErrorCode.PARAMS_ERROR);
        ThrowUtils.throwIf(spaceId == null, ErrorCode.PARAMS_ERROR);
        // 2. 空间权限校验
        Space space = spaceService.getById(spaceId);
        ThrowUtils.throwIf(space == null, ErrorCode.NOT_FOUND_ERROR, "空间不存在");
        // 3. 查询指定字段的图片
        List<Picture> pictureList = this.lambdaQuery().select(Picture::getId, Picture::getSpaceId)
                .eq(Picture::getSpaceId, spaceId)
                .in(Picture::getId, pictureIdList)
                .list();
        if (CollUtil.isEmpty(pictureList)) {
            return;
        }
        // 4. 批量更新分类和标签
        pictureList.forEach(picture -> {
            picture.setCategory(category);
            picture.setTags(JSONUtil.toJsonStr(tags));
        });
        // 5. 批量重命名(封装为方法)
        this.fillPictureWithNameRule(pictureList, nameRule);
        // 6. 数据库批量操作
        boolean updated = this.updateBatchById(pictureList);
        ThrowUtils.throwIf(!updated, ErrorCode.OPERATION_ERROR);
    }

    /**
     * AI 扩图任务请求
     *
     * @param request   请求参数
     * @param loginUser 登录用户
     * @return 创建任务结果
     */
    @Override
    public CreateOutPaintingTaskResponse createOutPaintingTask(CreatePictureOutPaintingTaskRequest request, User loginUser) {
        // 1. 参数校验
        ThrowUtils.throwIf(loginUser == null, ErrorCode.NOT_LOGIN_ERROR);
        Long pictureId = request.getPictureId();
        Picture picture = Optional.ofNullable(this.getById(pictureId))
                .orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND_ERROR, "图片不存在"));
        // this.checkPictureAuth(loginUser, picture);
        // 2. 创建任务
        CreateOutPaintingTaskRequest taskRequest = new CreateOutPaintingTaskRequest();
        CreateOutPaintingTaskRequest.Input input = new CreateOutPaintingTaskRequest.Input();
        input.setImageUrl(picture.getUrl());
        taskRequest.setInput(input);
        BeanUtil.copyProperties(request, taskRequest);
        return aliYunOutPaintingTask.createTask(taskRequest);
    }

    /**
     * 批量操作图片名称(规则: 图片{序号})
     *
     * @param pictureList 图片列表
     * @param nameRule    图片名称规则
     */
    private void fillPictureWithNameRule(List<Picture> pictureList, String nameRule) {
        if (CollUtil.isEmpty(pictureList) || StrUtil.isBlank(nameRule)) {
            return;
        }
        long count = 1;
        try {
            for (Picture picture : pictureList) {
                String pictureName = nameRule.replaceAll("\\{序号}", String.valueOf(count++));
                picture.setName(pictureName);
            }
        } catch (Exception e) {
            log.error("图片命名规则错误: {}", nameRule);
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "图片命名规则错误");
        }
    }


}





